/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
/*
 * Created on Dec 10, 2008
 */

package com.bigdata.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeoutException;

import org.apache.log4j.Logger;


/**
 * The default implementation used when scores are available.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
abstract public class AbstractServiceLoadHelperWithScores extends
        AbstractServiceLoadHelper {

    protected static final Logger log = Logger
            .getLogger(AbstractServiceLoadHelperWithScores.class);

    protected static final boolean INFO = log.isInfoEnabled();

    // protected static final boolean DEBUG = log.isDebugEnabled();

    final protected UUID knownGood;

    final protected ServiceScore[] scores;

    /**
     * @param joinTimeout
     *            The maximum time in milliseconds
     * @param knownGood
     *            A service that is known to be active and NOT excluded from the
     *            request to be posed.
     * @param scores
     *            Scores for the services in ascending order (least utilized to
     *            most utilized).
     */
    protected AbstractServiceLoadHelperWithScores(final long joinTimeout,
            final UUID knownGood, final ServiceScore[] scores) {

        super(joinTimeout);

        if (knownGood == null)
            throw new IllegalArgumentException();

        if (scores == null)
            throw new IllegalArgumentException();

        if (scores.length == 0)
            throw new IllegalArgumentException();

        this.knownGood = knownGood;

        this.scores = scores;

    }

    /**
     * Handles the case when we have per-service scores.
     * <p>
     * Note: Pre-condition: the service scores must exist and there must be at
     * least one active service with a score that is not excluded (the
     * {@link #knownGood} service).
     * 
     * @param minCount
     * @param maxCount
     * @param exclude
     * @return
     * 
     * @throws TimeoutException
     * @throws InterruptedException
     */
    public UUID[] getUnderUtilizedDataServices(final int minCount,
            final int maxCount, final UUID exclude) throws TimeoutException,
            InterruptedException {

        if (exclude != null && knownGood.equals(exclude)) {

            throw new IllegalArgumentException();

        }

        /*
         * Decide on the set of active services that we consider to be
         * under-utilized based on their scores. When maxCount is non-zero, this
         * set will be no larger than maxCount. When maxCount is zero the set
         * will contain all services that satisify the "under-utilized"
         * criteria.
         */
        final List<UUID> underUtilized = new ArrayList<UUID>(Math.max(
                scores.length, Math.max(minCount, maxCount)));

        int nok = 0;
        for (int i = 0; i < scores.length; i++) {

            final ServiceScore score = scores[i];

            // excluded?
            if (score.serviceUUID.equals(exclude))
                continue;

            // not active?
            if (!isActiveDataService(score.serviceUUID))
                continue;

            if (isUnderUtilizedDataService(score, scores)) {

                underUtilized.add(score.serviceUUID);

                nok++;

            }

            if (maxCount > 0 && nok >= maxCount) {

                if (INFO)
                    log.info("Satisifed maxCount=" + maxCount);

                break;

            }

            if (minCount > 0 && maxCount == 0 && nok >= minCount) {

                if (INFO)
                    log.info("Satisifed minCount=" + minCount);

                break;

            }

        }

        if (INFO)
            log.info("Found " + underUtilized.size()
                    + " under-utilized and non-excluded services");

        if (minCount > 0 && underUtilized.isEmpty()) {

            /*
             * Since we did not find anything we default to the one service that
             * provided as our fallback. This service might not be
             * under-utilized and it might no longer be active, but it is the
             * service that we are going to return.
             */

            assert knownGood != null;

            log.warn("Will report fallback service: " + knownGood);

            underUtilized.add(knownGood);

        }

        /*
         * Populate the return array, choosing at least minCount services and
         * repeating services if necessary.
         * 
         * Note: We return at least minCount UUIDs, even if we have to return
         * the same UUID in each slot.
         */

        final UUID[] uuids = new UUID[Math.max(minCount, nok)];

        int n = 0, i = 0;

        while (n < uuids.length) {

            final UUID tmp = underUtilized.get(i++ % nok);
            
            if (tmp == null)
                throw new AssertionError();
            
            if (exclude != null && tmp.equals(exclude))
                throw new AssertionError();
            
            uuids[n++] = tmp;

        }

        if (INFO)
            log.info("Reporting " + uuids.length
                    + " under-utilized and non-excluded services: "
                    + Arrays.toString(uuids));
        
        return uuids;

    }

}
